// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <vector>

#include "base/bind.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "net/base/address_family.h"
#include "net/base/address_list.h"
#include "net/base/fuzzed_data_provider.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/dns/fuzzed_host_resolver.h"
#include "net/dns/host_resolver.h"
#include "net/log/test_net_log.h"

namespace {

const char* kHostNames[] = { "foo", "foo.com", "a.foo.com",
    "bar", "localhost", "localhost6" };

net::AddressFamily kAddressFamilies[] = {
    net::ADDRESS_FAMILY_UNSPECIFIED,
    net::ADDRESS_FAMILY_IPV4,
    net::ADDRESS_FAMILY_IPV6,
};

class DnsRequest {
public:
    DnsRequest(net::HostResolver* host_resolver,
        net::FuzzedDataProvider* data_provider,
        std::vector<std::unique_ptr<DnsRequest>>* dns_requests)
        : host_resolver_(host_resolver)
        , data_provider_(data_provider)
        , dns_requests_(dns_requests)
        , handle_(nullptr)
        , is_running_(false)
    {
    }

    ~DnsRequest()
    {
        if (is_running_)
            Cancel();
    }

    // Creates and starts a DNS request using fuzzed parameters. If the request
    // doesn't complete synchronously, adds it to |dns_requests|.
    static void CreateRequest(
        net::HostResolver* host_resolver,
        net::FuzzedDataProvider* data_provider,
        std::vector<std::unique_ptr<DnsRequest>>* dns_requests)
    {
        std::unique_ptr<DnsRequest> dns_request(
            new DnsRequest(host_resolver, data_provider, dns_requests));

        if (dns_request->Start() == net::ERR_IO_PENDING)
            dns_requests->push_back(std::move(dns_request));
    }

    // If |dns_requests| is non-empty, waits for a randomly chosen one of the
    // requests to complete and removes it from |dns_requests|.
    static void WaitForRequestComplete(
        net::FuzzedDataProvider* data_provider,
        std::vector<std::unique_ptr<DnsRequest>>* dns_requests)
    {
        if (dns_requests->empty())
            return;
        uint32_t index = data_provider->ConsumeUint32InRange(0, dns_requests->size() - 1);

        // Remove the request from the list before waiting on it - this prevents one
        // of the other callbacks from deleting the callback being waited on.
        std::unique_ptr<DnsRequest> request = std::move((*dns_requests)[index]);
        dns_requests->erase(dns_requests->begin() + index);
        request->WaitUntilDone();
    }

    // If |dns_requests| is non-empty, attempts to cancel a randomly chosen one of
    // them and removes it from |dns_requests|. If the one it picks is already
    // complete, just removes it from the list.
    static void CancelRequest(
        net::HostResolver* host_resolver,
        net::FuzzedDataProvider* data_provider,
        std::vector<std::unique_ptr<DnsRequest>>* dns_requests)
    {
        if (dns_requests->empty())
            return;
        uint32_t index = data_provider->ConsumeUint32InRange(0, dns_requests->size() - 1);
        auto request = dns_requests->begin() + index;
        (*request)->Cancel();
        dns_requests->erase(request);
    }

private:
    void OnCallback(int result)
    {
        CHECK_NE(net::ERR_IO_PENDING, result);

        is_running_ = false;

        // Remove |this| from |dns_requests| and take ownership of it, if it wasn't
        // already removed from the vector. It may have been removed if this is in a
        // WaitForRequest call, in which case, do nothing.
        std::unique_ptr<DnsRequest> self;
        for (auto request = dns_requests_->begin(); request != dns_requests_->end();
             ++request) {
            if (request->get() != this)
                continue;
            self = std::move(*request);
            dns_requests_->erase(request);
            break;
        }

        while (true) {
            bool done = false;
            switch (data_provider_->ConsumeInt32InRange(0, 2)) {
            case 0:
                // Quit on 0, or when no data is left.
                done = true;
                break;
            case 1:
                CreateRequest(host_resolver_, data_provider_, dns_requests_);
                break;
            case 2:
                CancelRequest(host_resolver_, data_provider_, dns_requests_);
                break;
            }

            if (done)
                break;
        }

        if (run_loop_)
            run_loop_->Quit();
    }

    // Starts the DNS request, using a fuzzed set of parameters.
    int Start()
    {
        const char* hostname = data_provider_->PickValueInArray(kHostNames);
        net::HostResolver::RequestInfo info(net::HostPortPair(hostname, 80));
        info.set_address_family(data_provider_->PickValueInArray(kAddressFamilies));
        if (data_provider_->ConsumeBool())
            info.set_host_resolver_flags(net::HOST_RESOLVER_CANONNAME);

        net::RequestPriority priority = static_cast<net::RequestPriority>(data_provider_->ConsumeInt32InRange(
            net::MINIMUM_PRIORITY, net::MAXIMUM_PRIORITY));

        // Decide if should be a cache-only resolution.
        if (data_provider_->ConsumeBool()) {
            return host_resolver_->ResolveFromCache(info, &address_list_,
                net::BoundNetLog());
        }

        info.set_allow_cached_response(data_provider_->ConsumeBool());
        return host_resolver_->Resolve(
            info, priority, &address_list_,
            base::Bind(&DnsRequest::OnCallback, base::Unretained(this)), &handle_,
            net::BoundNetLog());
    }

    // Waits until the request is done, if it isn't done already.
    void WaitUntilDone()
    {
        CHECK(!run_loop_);
        if (is_running_) {
            run_loop_.reset(new base::RunLoop());
            run_loop_->Run();
            run_loop_.reset();
            CHECK(!is_running_);
        }
    }

    // Cancel the request, if not already completed. Otherwise, does nothing.
    void Cancel()
    {
        if (is_running_)
            host_resolver_->CancelRequest(handle_);
        is_running_ = false;
    }

    net::HostResolver* host_resolver_;
    net::FuzzedDataProvider* data_provider_;
    std::vector<std::unique_ptr<DnsRequest>>* dns_requests_;

    net::HostResolver::RequestHandle handle_;
    net::AddressList address_list_;

    bool is_running_;

    std::unique_ptr<base::RunLoop> run_loop_;

    DISALLOW_COPY_AND_ASSIGN(DnsRequest);
};

} // namespace

// Fuzzer for HostResolverImpl. Fuzzes using both the system resolver and
// built-in DNS client paths.
//
// TODO(mmenke): Add coverage for things this does not cover:
//     * Out of order completion, particularly for the platform resolver path.
//     * Simulate network changes, including both enabling and disabling the
//     async resolver while lookups are active as a result of the change.
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
    {
        net::FuzzedDataProvider data_provider(data, size);
        net::TestNetLog net_log;

        net::HostResolver::Options options;
        options.max_concurrent_resolves = data_provider.ConsumeUint32InRange(1, 8);
        options.enable_caching = data_provider.ConsumeBool();
        net::FuzzedHostResolver host_resolver(options, &net_log, &data_provider);
        host_resolver.SetDnsClientEnabled(data_provider.ConsumeBool());

        std::vector<std::unique_ptr<DnsRequest>> dns_requests;
        while (true) {
            switch (data_provider.ConsumeInt32InRange(0, 3)) {
            case 0:
                // Quit on 0, or when no data is left.
                return 0;
            case 1:
                DnsRequest::CreateRequest(&host_resolver, &data_provider,
                    &dns_requests);
                break;
            case 2:
                DnsRequest::WaitForRequestComplete(&data_provider, &dns_requests);
                break;
            case 3:
                DnsRequest::CancelRequest(&host_resolver, &data_provider,
                    &dns_requests);
                break;
            }
        }
    }

    // Clean up any pending tasks, after deleting everything.
    base::RunLoop().RunUntilIdle();

    return 0;
}
